﻿using Elsa;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Persistence.Specifications;
using Elsa.Services;
using Elsa.Services.Models;
using NodaTime;
using Open.Linq.AsyncExtensions;
using SqlSugar;
using Zhp.Common.Exception;
using Zhp.Common.Extention;
using Zhp.Common.Helper;
using Zhp.Common.WebApp;
using Zhp.SqlSugar.UnitOfWork;
using Zhp.Workflow.Activities;
using Zhp.Workflow.Activities.DataOptions;
using Zhp.Workflow.Entity;
using Zhp.Workflow.Models;
using Zhp.Workflow.Notify.Services;
using LogRecord = Elsa.Persistence.Specifications.WorkflowExecutionLogRecords;

namespace Zhp.Workflow.Services
{
    public class WorkflowService : IWorkflowService
    {
        private readonly ICurrentUser _current;
        private readonly ISqlSugarClient _dbContext;
        private readonly IWorkflowLaunchpad _workflowLaunchpad;
        private readonly IWorkflowInstanceDeleter _workflowDeleter;
        private readonly IWorkflowInstanceStore _workflowInstanceStore;
        private readonly IWorkflowExecutionLogStore _workflowExecutionLogStore;
        private readonly IBookmarkFinder _bookmarkFinder;
        private readonly ITodoService _todoService;
        private readonly IWorkflowDefinitionStore _workflowDefinitionStore;
        public WorkflowService(ICurrentUser current, 
            IUnitOfWork<SugarUnitOfWork> unitOfWork, 
            IWorkflowLaunchpad workflowLaunchpad, 
            IWorkflowInstanceDeleter workflowDeleter, 
            IWorkflowInstanceStore workflowInstanceStore, 
            IWorkflowExecutionLogStore workflowExecutionLogStore, 
            IBookmarkFinder bookmarkFinder, 
            ITodoService todoService,
            IWorkflowDefinitionStore workflowDefinitionStore)
        {
            _current = current;
            _dbContext = unitOfWork.Db;
            _workflowLaunchpad = workflowLaunchpad;
            _workflowDeleter = workflowDeleter;
            _workflowInstanceStore = workflowInstanceStore;
            _workflowExecutionLogStore = workflowExecutionLogStore;
            _bookmarkFinder = bookmarkFinder;
            _todoService = todoService;
            _workflowDefinitionStore = workflowDefinitionStore;
        }

        /// <summary>
        /// 开始流程
        /// </summary>
        /// <param name="entityId"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task<RunWorkflowResult> StartWorkflowAsync<T>(long entityId)
        {
            var entity = await _dbContext.Queryable<T>().InSingleAsync(entityId);
            if (entity != null)
            {
                return await StartWorkflowAsync<T>(entityId, ((IBaseWorkflow)entity).WorkflowName);
            }
            return null;
        }

        public async Task<RunWorkflowResult> StartWorkflowAsync<T>(long entityId, string flowName)
        {
            var definition = await _dbContext.Queryable<Elsa_WorkflowDefinition>().Select(x => new { x.DefinitionId, x.Name }).FirstAsync(x => x.Name == flowName);
            if (definition != null)
            {
                var workflow = await _workflowLaunchpad.FindStartableWorkflowAsync(definition.DefinitionId, contextId: entityId.ToString());
                if (workflow != null)
                {
                    workflow.WorkflowInstance.Variables.Set(WorkflowVariables.SubmiterId, _current.UserId);
                    workflow.WorkflowInstance.Variables.Set(WorkflowVariables.SubmiterName, $"{_current.NickName}({_current.Name})");
                    workflow.WorkflowInstance.Name = definition.Name;

                    var rv = await _workflowLaunchpad.ExecuteStartableWorkflowAsync(workflow, new WorkflowInput
                    {
                        Input = new ApproveInput
                        {
                            Action = ApproveActionEnum.Submit,
                            UserId = _current.UserId,
                            UserName = _current.Name,
                            NickName = _current.NickName,
                            Remark = ""
                        }
                    });
                    if (rv?.WorkflowInstance?.Faults?.Count > 0)
                    {
                        throw new Exception(rv.Exception.Message);
                    }
                    else
                    {
                        return rv;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// 审批流程，继续执行流程
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="approve"></param>
        /// <returns></returns>
        public async Task<RunWorkflowResult> ContinueWorkflowAsync<T>(BaseApproveInfo approve)
        {
            var entity = await _dbContext.Queryable<T>().InSingleAsync(approve.Id);
            var query = new WorkflowsQuery(nameof(ApproveActivity), new ApproveBookmark(_current.UserId.ToString(), ((IBaseWorkflow)entity).WorkflowName, approve.Tag, approve.Id.ToString()), null, null, null, null);
            if (query != null)
            {
                var flows = await _workflowLaunchpad.FindWorkflowsAsync(query);
                foreach (var item in flows)
                {
                    if (item.WorkflowInstance == null)
                    {
                        var rv = await _workflowLaunchpad.ExecutePendingWorkflowAsync(item, new WorkflowInput 
                        { 
                            Input = new ApproveInput 
                            { 
                                Action = approve.Action.ToEnum<ApproveActionEnum>(),
                                UserId = _current.UserId, 
                                UserName = _current.Name, 
                                NickName = _current.NickName, 
                                Remark = approve.Remark
                            } 
                        });

                        return rv;
                    }
                }
            }

            foreach (var roleId in _current.RoleIds)
            {
                query = new WorkflowsQuery(nameof(ApproveActivity), new ApproveBookmark(roleId.ToString(), ((IBaseWorkflow)entity).WorkflowName, approve.Tag, approve.Id.ToString()), null, null, null, null);
                if (query != null)
                {
                    var flows = await _workflowLaunchpad.FindWorkflowsAsync(query);
                    foreach (var item in flows)
                    {
                        if (item.WorkflowInstance == null)
                        {
                            var rv = await _workflowLaunchpad.ExecutePendingWorkflowAsync(item, new WorkflowInput 
                            {
                                Input = new ApproveInput
                                {
                                    Action = approve.Action.ToEnum<ApproveActionEnum>(),
                                    UserId = _current.UserId,
                                    UserName = _current.Name,
                                    NickName = _current.NickName,
                                    Remark = approve.Remark
                                }
                            });

                            return rv;
                        }
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// 取消流程
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entityId"></param>
        /// <returns></returns>
        /// <exception cref="AlertException"></exception>
        public async Task<bool> CancelWorkflowAsync<T>(long entityId)
        {
            bool result = false;
            var entity = await _dbContext.Queryable<T>().InSingleAsync(entityId);
            var obj = (IBaseWorkflow)entity;
            if (obj.ApprovedCount > 0)
            {
                throw new AlertException("该流程已被开始审批不能取消！");
            }
            using (var uow = _dbContext.CreateContext(_dbContext.Ado.IsNoTran()))
            {
                obj.Cancel();
                await _dbContext.UpdateableByObject(obj).ExecuteCommandAsync();

                var workflowInstance = await _workflowInstanceStore.FindByIdAsync(obj.WorkflowInstanceId);

                await _dbContext.Deleteable<Elsa_WorkflowExecutionLogRecord>(x => x.WorkflowInstanceId == obj.WorkflowInstanceId).ExecuteCommandAsync();
                await _dbContext.Deleteable<Elsa_Bookmark>(x => x.WorkflowInstanceId == obj.WorkflowInstanceId).ExecuteCommandAsync();
                await _dbContext.Deleteable<Elsa_WorkflowInstance>(x => x.Id == obj.WorkflowInstanceId).ExecuteCommandAsync();

                //发送取消邮件通知
                if (workflowInstance != null)
                {
                    await _todoService.InsertTodo(workflowInstance, WorkflowStatusEnum.Cancelled);
                }

                result = uow.Commit();
            }
            return result;
        }

        /// <summary>
        /// 删除表单数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entityId"></param>
        /// <returns></returns>
        /// <exception cref="AlertException"></exception>
        public async Task<bool> DeleteWorkflowAsync<T>(long entityId)
        {
            bool result = false;
            var entity = await _dbContext.Queryable<T>().InSingleAsync(entityId);
            var obj = (IBaseWorkflow)entity;
            if (obj.WorkflowStatus == WorkflowStatusEnum.InProgress || obj.WorkflowStatus == WorkflowStatusEnum.Finished)
            {
                throw new AlertException("流程中或结束的不能删除！");
            }
            using (var uow = _dbContext.CreateContext(_dbContext.Ado.IsNoTran()))
            {
                await _dbContext.DeleteableByObject(obj).ExecuteCommandAsync();

                await _dbContext.Deleteable<Elsa_WorkflowExecutionLogRecord>(x => x.WorkflowInstanceId == obj.WorkflowInstanceId).ExecuteCommandAsync();
                await _dbContext.Deleteable<Elsa_Bookmark>(x => x.WorkflowInstanceId == obj.WorkflowInstanceId).ExecuteCommandAsync();
                await _dbContext.Deleteable<Elsa_WorkflowInstance>(x => x.Id == obj.WorkflowInstanceId).ExecuteCommandAsync();

                result = uow.Commit();
            }

            return result;
        }

        /// <summary>
        /// 查询是否有审批按钮
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public async Task SetApproveInfo(IEnumerable<BaseWorkflowInfo> list)
        {
            var roleIds = _current.RoleIds.ConvertAll(x => x.ToString());
            var bookmarks = await _bookmarkFinder.FindBookmarksByTypeAsync<ApproveBookmark>();
            var pendingList = await GetSelfPendingList();
            foreach (var item in list)
            {
                item.ApproveStatusText = item.ApproveStatus.GetDescription();
                item.WorkflowStatusText = item.WorkflowStatus.GetDescription();
                var approve = pendingList.FirstOrDefault(x => x.EntityId == item.Id.ToString());
                if (approve != null)
                {
                    item.ApproveText = string.IsNullOrEmpty(approve.Tag) ? "审批" : approve.Tag;
                }
            }
        }

        /// <summary>
        /// 查询待审批数据
        /// </summary>
        /// <returns></returns>
        public async Task<List<ApproveBookmark>> GetSelfPendingList()
        {
            var entityIdList = new List<ApproveBookmark>();
            var roleIds = _current.RoleIds.ConvertAll(x => x.ToString());
            var bookmarks = await _bookmarkFinder.FindBookmarksByTypeAsync<ApproveBookmark>();
            foreach (var book in bookmarks)
            {
                var approve = book.Model.ToObject<ApproveBookmark>();
                if ((approve.User == _current.UserId.ToString() || roleIds.Contains(approve.User)))
                {
                    entityIdList.Add(approve);
                }
            }
            return entityIdList;
        }

        /// <summary>
        /// 用于列表查询待审批数据
        /// </summary>
        /// <returns></returns>
        public async Task<List<long>> GetSelfPendingEntityIdList()
        {
            var entityIdList = new List<long>();
            var roleIds = _current.RoleIds.ConvertAll(x => x.ToString());
            var bookmarks = await _bookmarkFinder.FindBookmarksByTypeAsync<ApproveBookmark>();
            foreach (var book in bookmarks)
            {
                var approve = book.Model.ToObject<ApproveBookmark>();
                if ((approve.User == _current.UserId.ToString() || roleIds.Contains(approve.User)))
                {
                    entityIdList.Add(Convert.ToInt64(approve.EntityId));
                }
            }
            return entityIdList;
        }

        /// <summary>
        /// 查询审批记录
        /// </summary>
        /// <param name="entityId"></param>
        /// <returns></returns>
        public async Task<List<ApproveTimeLine>> GetApproveTimeLine<T>(long entityId)
        {
            var entity = await _dbContext.Queryable<T>().InSingleAsync(entityId);
            var line = new List<ApproveTimeLine>();
            var workflowIds = _dbContext.Queryable<Elsa_WorkflowInstance>().Where(x => x.Name == ((IBaseWorkflow)entity).WorkflowName && x.ContextType == typeof(T).FullName && x.ContextId == entityId.ToString()).OrderBy(x => x.CreatedAt).Select(x => x.Id).ToList();
            foreach (var workflowId in workflowIds)
            {
                var instance = await _workflowInstanceStore.FindByIdAsync(workflowId);
                if (instance != null)
                {
                    var specification = new LogRecord.WorkflowInstanceIdSpecification(workflowId);
                    var orderBy = OrderBySpecification.OrderBy<WorkflowExecutionLogRecord>(x => x.Timestamp);
                    var logs = await _workflowExecutionLogStore.FindManyAsync(specification, orderBy).ToList();
                    var rv = logs.Where(x => x.ActivityType == nameof(ApproveActivity) && x.EventName != "Executing" && x.EventName != "Resuming" && x.EventName != "Suspended")
                                .Select(x => new ApproveTimeLine
                                {
                                    Id = x.ActivityId,
                                    Time = x.Timestamp.InZone(DateTimeZoneProviders.Tzdb.GetSystemDefault()).ToString("yyyy-MM-dd HH:mm:ss", null),
                                    Action = x.EventName == "Executed" ? "等待审批" : x.Data.ContainsKey("Outcomes") ? x.Data["Outcomes"].Values<string>().FirstOrDefault() : "",
                                    Remark = "",
                                    Approvers = "",
                                    Approved = ""
                                }).ToList();

                    rv = rv.Where(x => string.IsNullOrEmpty(x.Action) == false).ToList();
                    foreach (var record in rv)
                    {
                        var ad = instance.ActivityData[record.Id];
                        object approved, remark, approvers;
                        ad.TryGetValue(nameof(ApproveActivity.ApprovedBy), out approved);
                        ad.TryGetValue(nameof(ApproveActivity.Remark), out remark);
                        ad.TryGetValue(nameof(ApproveActivity.ApproveUsersFullText), out approvers);
                        record.Approved = (approved as string) ?? "";
                        record.Approvers = string.Join(",", (approvers as List<string>));
                        if (record.Action != "等待审批")
                        {
                            record.Remark = (remark as string) ?? "";
                        }
                    }
                    ApproveTimeLine first = new ApproveTimeLine
                    {
                        Action = "_start",
                        Approved = instance.Variables.Get(WorkflowVariables.SubmiterName)?.ToString() ?? "",
                        Approvers = "",
                        Id = "",
                        Remark = "",
                        Time = instance.CreatedAt.InZone(DateTimeZoneProviders.Tzdb.GetSystemDefault()).ToString("yyyy-MM-dd HH:mm:ss", null)
                    };
                    rv.Insert(0, first);
                    if (instance.FinishedAt != null)
                    {
                        ApproveTimeLine last = new ApproveTimeLine
                        {
                            Action = "_finish",
                            Approved = "",
                            Approvers = "",
                            Id = "",
                            Remark = "",
                            Time = instance.FinishedAt.Value.InZone(DateTimeZoneProviders.Tzdb.GetSystemDefault()).ToString("yyyy-MM-dd HH:mm:ss", null)
                        };
                        rv.Add(last);
                    }
                    line.AddRange(rv);
                }
            }

            return line;
        }

        public async Task<List<string>> QueryFlowNameSelect()
        {
            return await _dbContext.Queryable<Elsa_WorkflowDefinition>().Where(x => x.IsLatest).Select(x => x.Name).ToListAsync();
        }

        /// <summary>
        /// 待审批数量
        /// </summary>
        /// <returns></returns>
        public async Task<int> PendingApproveCount()
        {
            int count = 0;
            var roleIds = _current.RoleIds.ConvertAll(x => x.ToString());
            var bookmarks = await _bookmarkFinder.FindBookmarksByTypeAsync<ApproveBookmark>();
            foreach (var book in bookmarks)
            {
                var approve = book.Model.ToObject<ApproveBookmark>();
                if ((approve.User == _current.UserId.ToString() || roleIds.Contains(approve.User)))
                {
                    count++;
                }
            }
            return count;
        }

        /// <summary>
        /// 已审批数量
        /// </summary>
        /// <returns></returns>
        public async Task<int> ApprovedCount()
        {
            return await _dbContext.Queryable<WorkflowApproverRecord>().CountAsync(x => x.UserId == _current.UserId);
        }

        /// <summary>
        /// 获取枚举下拉框数据
        /// </summary>
        /// <param name="enumType"></param>
        /// <returns></returns>
        public async Task<List<WorkflowEnumModel>> GetEnumList<T>()
        {
            var list = new List<WorkflowEnumModel>();
            var type = typeof(T);

            var fields = type.GetEnumFields();
            foreach (var field in fields)
            {
                list.Add(new WorkflowEnumModel
                {
                    Name = field.Name,
                    Value = field.GetValue(type).GetHashCode(),
                    Text = field.GetDescriptionFirst()
                });
            }

            return await Task.FromResult(list);
        }
    }
}
